<?php
/**
 * mint模板类
 *
 * @version $Id: IMintTemplate.php 312 2008-04-23 16:29:38Z iwind $
 * @package util.mint
 */
class IMintTemplate extends IObject {
	/**
	 * Enter description here...
	 *
	 * @var IMintDataSet
	 */
	private static $dataSet;
	
	/**
	 * 模板名
	 *
	 * @var string
	 */
	private $templateName;
	
	/**
	 * 模板内容
	 *
	 * @var string
	 */
	private $text;
	
	/**
	 * 模板文档
	 *
	 * @var IMintDocument
	 */
	private $document;
	
	/**
	 * 模板格式
	 *
	 * @var string
	 */
	private $format;

	
	/**
	 * 当前模板需要监视的文件
	 *
	 * 当所监视的文件有变动时，就会重新编译
	 * 
	 * @var array
	 */
	private $observedFiles = array();
	
	/**
	 * 编译后的源文件
	 *
	 * @var IFile
	 */
	private $compiledSourceFile;
	
	/**
	 * 编译后的数据文件
	 *
	 * @var IFile
	 */
	private $compiledDataFile;
	
	/**
	 * 模板文件
	 *
	 * @var IFile
	 */
	private $templateFile;
	
	/**
	 * 构造方法
	 *
	 * @param string $templateName 模板名称
	 * @param string $format 模板格式，默认为 IMintConfig::HTML_FORMAT
	 */
	public function __construct($templateName, $format = IMintConfig::HTML_FORMAT) {
		if (self::$dataSet == null) {
			self::$dataSet = new IMintDataSet();
		}
		$this->templateName = $templateName;
		$this->format = $format;
		$this->templateFile = IMint::getFile($this->templateName, $format);
	}
	
	/**
	 * 分析并编译模板文件，并将结果写入新的文件
	 *
	 */
	private function parse() {
		//检查文件
		$compiledFileName = md5($this->templateFile->getAbsolutePath()) . "_" . $this->templateFile->getName();
		$this->compiledSourceFile = new IFile(IMint::getOption("compile-dir") . "/" . $compiledFileName . "_compile.php");
		$this->compiledDataFile = new IFile(IMint::getOption("compile-dir") . "/" . $compiledFileName . "_data.php");
		
		//运行模式
		$mode = IMint::getOption("mode");
		if ($mode != "debug" && $this->compiledDataFile->exists()) {
			if ($mode ==  "static" && $this->compiledSourceFile->exists()) {//static
				return;
			}
			
			//user
			require_once($this->compiledDataFile->getPath());
			if (isset($requireFiles) && $this->isNew($requireFiles, $templateLastModified)) {
				if ($this->compiledSourceFile->exists()) {
					$this->exec();
					return;
				}
			}
		}
		
		//读文件
		$this->text = $this->templateFile->getReader()->read();
		
		//处理注释
		$this->text = $this->doComments($this->text);
		
		//保存变量
		$wholeRegexp = IMintVariable::getWholeRegexp();
		foreach (IMintConfig::$variableMarkArray as $mark) {
			$mark = preg_quote($mark, "/");
			$this->text = preg_replace_callback("/(?:\{(\*?{$mark}))({$wholeRegexp})(\})/iU", array("self", "getTmpMark"), $this->text);
			$this->text = preg_replace_callback("/(?:\{(\*?{$mark}))\{({$wholeRegexp})\}(\})/iU", array("self", "getTmpMark"), $this->text);
			$this->text = preg_replace_callback("/(?:%7B(\*?{$mark}))({$wholeRegexp})(%7D)/iU", array("self", "getTmpMark"), $this->text);
			$this->text = preg_replace_callback("/(?:%7B(\*?{$mark}))%7B({$wholeRegexp})%7D(%7D)/iU", array("self", "getTmpMark"), $this->text);
		}
		
		//添加监视文件
		$this->addObservedFile($this->templateFile);

		if (!isset(IMintConfig::$formats[$this->format])) {
			$this->format = IMintConfig::HTML_FORMAT;
		}
		$documentClass = IMintConfig::$formats[$this->format];
		$this->document = IObjectFactory::getObject($documentClass);
		
		$parseCounter = 0;
		while (true) {
			$parseCounter ++;
			if ($parseCounter > IMintConfig::$maxParseLevel) {
				break;
			}
			
			//处理taglib
			$this->text = $this->parseTagLib($this->text);
			
			$this->document->load($this->text);
			$domDocument = $this->document->getDOMDocument();
			$xpath = new DOMXPath($domDocument);
			
			$nodeList = $xpath->query("//*[@" . IMintConfig::$componentAttributeName . "]", $domDocument);
			if ($nodeList == null || $nodeList->length == 0) {
				break;
			}
			
			//初始化
			$componentList = array();
			foreach ($nodeList as $node) {
				$attr = $node->attributes->getNamedItem(IMintConfig::$componentAttributeName);
				$componentName = $attr->value;
				if (isset(IMintConfig::$components[$componentName])) {
					$componentClassName = IMintConfig::$components[$componentName]["class"];
					$componentObject = new $componentClassName();
					$componentObject->setNode($node);
					$componentObject->setDataSet(self::$dataSet);
					$componentObject->setTemplate($this);
					$componentList[] = $componentObject;
				}
				else {
					throw new IMintException("unknown component named by '{$componentName}'");
				}
			}
			
			//检验
			foreach ($componentList as $componentObject) {
				if (!$componentObject->validate()) {
					throw new IMintException("component '{$componentName}' validate failed"); 
				}
			}			
			
			//处理
			foreach ($componentList as $componentObject) {
				$componentObject->process(); 
			}
			
			//处理文本
			$this->text = $this->document->__toString();
			$this->text = strtr($this->text, IMintReplacement::getTextData());
		}
		
		

		//替换变量
		$this->text = strtr($this->text, IMintReplacement::getScriptData());
		foreach (IMintConfig::$variableMarkArray as $mark) {
			$mark = preg_quote($mark, "/");
			$this->text = preg_replace_callback("/(?:\{(\*?{$mark}))({$wholeRegexp})(\})/iU", array("self", "variableReplaceCallBack"), $this->text);
			$this->text = preg_replace_callback("/(?:\{(\*?{$mark}))\{({$wholeRegexp})\}(\})/iU", array("self", "variableReplaceCallBack"), $this->text);
			$this->text = preg_replace_callback("/(?:%7B(\*?{$mark}))({$wholeRegexp})(%7D)/iU", array("self", "variableReplaceEncodeCallBack"), $this->text);
			$this->text = preg_replace_callback("/(?:%7B(\*?{$mark}))%7B({$wholeRegexp})%7D(%7D)/iU", array("self", "variableReplaceEncodeCallBack"), $this->text);
		}
		
		foreach (IMintConfig::$variableMarkArray as $mark) {
			$mark = preg_quote($mark, "/");
			$this->text = preg_replace("/(\{|%7B)(?:\\\)({$mark})/i", "\\1\\2", $this->text);
		}
		
		//替换包含在组件里的变量
		$this->text = strtr($this->text, IMintReplacement::getScriptData());
		
		///将 else 之前的语句连起来
		$this->text = preg_replace("/<\\?php \} \?>\s*<\?php else \{ \?>/isU", "\\1<?php } else { ?>", $this->text);
		
		//转义<?xml..
		$this->text = preg_replace("/^\s*\<\?(?!php)/si", "<?php echo \"\\0\";?>", $this->text);
		
		//定义dataSet
		$this->text = "<?php \$TDataSet=&self::\$dataSet;\n ?>" . $this->text;
		
		//去除临时节点
		$this->text = preg_replace("/<\\/?MINT_TMP_NODE.*>/isU", null, $this->text);
	
		//生成编译后的文件
		$sourcdeWriter = $this->compiledSourceFile->getWriter();
		$sourcdeWriter->write($this->text);
		$sourcdeWriter->close();
		
		//写入需要检查的和模板相关的数据
		$dataWriter = $this->compiledDataFile->getWriter();
		$data = "<?php \$requireFiles = array();\n";
		foreach ($this->observedFiles as $file) {
			$data .= "\$requireFiles[] = '" . $file->getAbsolutePath() . "';\n";
		}
		$data .= " \$templateLastModified= '" . time() . "';\n";
		$data .= " ?>";
		$dataWriter->write($data);
		$dataWriter->close();
	}
	
	/**
	 * 处理taglib
	 *
	 * @param string $text 文本
	 * @return string
	 * @since 0.2
	 */
	private function parseTagLib($text) {
		foreach (IMintConfig::$components as $comName=>$component) {
			if (!is_null($component["prefix"])) {
				$prefix = $component["prefix"];
				$name = preg_replace("/[^\\w]+/", "", $comName);
				$text = preg_replace("/<{$prefix}\\s*:\\s*{$name}(.*)>/isU", "<MINT_TMP_NODE x-keep=\"true\" com=\"{$comName}\" \\1>", $text);
				$text = preg_replace("/<{$prefix}\\s*:\\s*{$name}(.*)\\/?>/isU", "<MINT_TMP_NODE x-keep=\"true\" com=\"{$comName}\"\\1</{$name}>", $text);
				$text = preg_replace("/<\\/{$prefix}\\s*:\\s*{$name}>/is", "</MINT_TMP_NODE>", $text);
			}
		}
		return $text;
	}
	
	/**
	 * 取得编译后的代码文本
	 *
	 * @return string
	 */
	public function getCompiledText() {
		$this->parse();
		return $this->compiledSourceFile->getReader()->read();
	}
	
	/**
	 * 输出解析后的结果
	 *
	 */
	public function p() {
		$this->parse();
		$this->exec();
	}
	
	/**
	 * Enter description here...
	 *
	 * @since 0.1
	 */
	private function exec() {
//		try {
			require_once $this->compiledSourceFile->getPath();
//		} catch (Exception $e) {
//			throw $e;
//		}
	}
	
	/**
	 * 设置变量值
	 *
	 * @param string $name 变量名
	 * @param mixed $value 变量值
	 */
	public function setValue($name, $value) {
		self::$dataSet->set($name, $value);
	}
	

	
	/**
	 * 添加需要监视的文件
	 *
	 * @param IFile $file 文件对象
	 */
	public function addObservedFile(IFile $file) {
		$this->observedFiles[] = $file;
	}
	
	/**
	 * 检查一批文件相对于某个特定时间是否是新的，即文件的修改时间在该特定时间之后
	 *
	 * @param array $files 文件列表
	 * @param int $time 比较用的时间戳
	 * @return boolean
	 */
	private function isNew($files, $time) {
		foreach ($files as $file) {
			if (filemtime($file) > $time) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 取得模板文件
	 *
	 * @return IFile
	 */
	public function getTemplateFile() {
		return $this->templateFile;
	}
	
	/**
	 * 取得模板中使用的记录集
	 *
	 * @return IMintDataSet
	 */
	public function getDataSet() {
		return self::$dataSet;
	}
	
	/**
	 * 取得分析后的文本
	 *
	 * @return string
	 * @since 0.0.2
	 */
	public function getParsedText() {
		ob_start(array("self", "bufferHandler"));
		$this->p();
		$text = ob_get_contents();
		ob_end_clean();
		return $text;
	}
	
	private static function bufferHandler($buffer) {
		return $buffer;
	}
	
	/**
	 * 变量替换时使用的处理匹配内容的回调函数
	 *
	 * @param array $match 匹配的内容
	 * @return string
	 * @since 0.0.2
	 */
	private static function variableReplaceCallBack($match) {
		$var = htmlspecialchars_decode($match[1] . $match[2]);
		$variable = new IMintVariable($var);
		$varname = $variable->getVarname();
		if (!$variable->isValid()) {
			return $match[0];
		}
		return "<?php \n" . $variable->getParseProcess() . " echo {$varname}; ?>";
	}
	
	/**
	 * 变量替换时使用的处理匹配内容的回调函数
	 *
	 * 与variableReplaceCallBack不同的是，它是用来处理经过urlencode之后的结果
	 * 
	 * @param array $match 匹配的内容
	 * @return string
	 * @since 0.0.2
	 */	
	private static function variableReplaceEncodeCallBack($match) {
		$match[2] =  htmlspecialchars_decode(urldecode($match[2]));
		$variable = new IMintVariable(urldecode($match[1] . $match[2]));
		$varname = $variable->getVarname();
		if (!$variable->isValid()) {
			return $match[0];
		}
		return "<?php \n " . $variable->getParseProcess() . "  echo {$varname};   ?>";
	}
	
	private static function getTmpMark($match) {
		//如果是以yield开头的，不处理
		if (strstr($match[0], "{\$yield")) {
			return $match[0];
		}
		return IMintReplacement::add($match[0]);
	}

	
	/**
	 * 处理注释
	 *
	 * @param string $text 文本
	 * @return string
	 * @since 0.0.3
	 */
	private function doComments($text) {
		//<!-- x-comment: please do not removed below definations -->
		$text = preg_replace("/<!--\s*x-comment:\s.+-->\r?\n?/siU", "", $text);
		return $text;
	}
	
	/**
	 * Enter description here...
	 *
	 * @param array $values
	 * @since 0.1
	 */
	public function addValues(array $values = null) {
		if (is_array($values)) {
			foreach ($values as $key=>$value) {
				self::$dataSet->set($key, $value);
			}
		}
	}
}

?>